(ns lastfm-music-recommendation.db.datomic
  (:use [datomic.api :only [db q] :as d])
  (:require [clojure.string :as s]
            [lastfm-music-recommendation.util :as util]))

(def uri "datomic:free://localhost:4334/lastfm")

;;only the first time
;(d/create-database uri)

(def conn (d/connect uri))

;;Only the first time
;@(d/transact conn (read-string (slurp "lastfm-schema.dtm")))

;for crawler:

(defn get-links-to-visit
  "Returns links to visit from db"
  []
  (ffirst (q '[:find (sample 30 ?link) :where
               [?l :link/visited "false"]
               [?l :link/link ?link]] (db conn))))

(defn save-link
  "Inserts new link"
  [link]
  (let [res (q '[:find ?l :in $ ?link :where 
                 [?l :link/link ?link]] (db conn) link)]
    (if (empty? res)
      @(d/transact conn [{:db/id (d/tempid :db.part/user)
                          :link/link link
                          :link/visited "false"}]))))

 (save-link "http://www.last.fm/music/Madonna/Confessions+on+a+Dance+Floor/Future+Lovers")

(defn set-visited!
  "Set link's visited attribute to 'true'"
  [link]
  (let [lid (ffirst (q '[:find ?id :in $ ?link :where
                         [?id :link/link ?link]] (db conn) link))]
     @(d/transact conn [{:db/id lid
                         :link/visited "true"}])))

;for songs:

(defn insert-song
  "Saves new song, with all it's words" 
  [song url]
  (let [res (q '[:find ?s :in $ ?song :where
                 [?s :song/song ?song]] (db conn) song)]
    (if (empty? res)
      (let [lid (ffirst (q '[:find ?id :in $ ?url :where
                             [?id :link/link ?url]] (db conn) url))
            word-tx (map (fn [word] {:db/id #db/id[:db.part/user -1] 
                                     :song/word (s/lower-case word)}) 
                         (util/split-song song))]
        @(d/transact conn (apply merge [{:db/id #db/id[:db.part/user -1]
                                         :song/song song :song/link lid}]
                                 word-tx))
        (println song)))))

;*this is called when user is saving recommended songs
(defn save-song
  "Inserts song & url into db and adds it to user"
  [user sng url]
  (let [song (util/song-to-string sng)
        u (save-link url)
        s (insert-song song url)        
        sid (ffirst (q '[:find ?id :in $ ?song
                         :where [?id :song/song ?song]] (db conn) song))
        uid (ffirst (q '[:find ?uid :in $ ?user
                         :where [?uid :user/username ?user]] (db conn) user))]
    @(d/transact conn [{:db/id uid
                        :user/song sid}])))
;*
(defn save
  "Saves recommended songs into db"
  [user songs]
  (let [a (map #(save-song user (ffirst %1) (:link (first %1))) 
               songs)]
    (not (empty? a))))

;*
(defn get-saved
  "Returns saved songs from db for user"
  [user]
  (q '[:find ?t :in $ ?user :where
       [?u :user/username ?user]
       [?u :user/song ?s]
       [?s :song/song ?t]] (db conn) user))

;for user:

(defn register-user
  "If the user doesn't exist, inserts the new user into db"
  [user pass]
  (if (empty? (q '[:find ?u :in $ ?user :where 
                   [?u user/username ?user]] (db conn) user))
    @(d/transact conn [{:db/id (d/tempid :db.part/user)
                        :user/username user
                        :user/password pass}])))

(defn login
  "Username and password validation"
  [user pass]
  (let [u (q '[:find ?u :in $ ?user ?pass :where 
               [?u user/username ?user]
               [?u user/password ?pass]] (db conn) user pass)]
    (not (empty? u))))

;for search:

(defn find-by-keyword
  [keyword]
  (q '[:find ?song ?url ?time :in $ ?key :where
       [?s :song/word ?key]
       [?s :song/link ?link]
       [?s :song/song ?song ?tx]
       [?link :link/link ?url]
       [?tx :db/txInstant ?time]] (db conn) (s/lower-case keyword)))

;for neural network:

(defn get-strength-wordhidden
  [word hidden]
  (let [res (q '[:find ?s :in $ ?w ?hid :where
                 [?c :nn/hiddennode ?hid]
                 [?c :nn/word ?w]
                 [?c :nn/constrength ?s]] (db conn) word hidden)]
    (if (empty? res) (- 0.2) (ffirst res))))

(defn get-strength-hiddensong
  [hidden song]
  (let [res  (q '[:find ?cs :in $ ?s ?hid :where
                  [?c :nn/hiddennode ?hid]
                  [?c :nn/song ?s]
                  [?c :nn/constrength ?cs]] (db conn) song hidden)]
    (if (empty? res) 0 (ffirst res))))

(defn set-strength-wordhidden
  [word hidden strength]
  (let [res (q '[:find ?c :in $ ?w ?hid :where
                 [?c :nn/hiddennode ?hid]
                 [?c :nn/word ?w]] (db conn) word hidden)]
    (if (empty? res)
        @(d/transact conn [{:db/id (d/tempid :db.part/user)
                          :nn/word word
                          :nn/hiddennode hidden
                          :nn/constrength (float strength)}])
        @(d/transact conn [{:db/id (ffirst res)
                          :nn/constrength (float strength)}]))))

(defn set-strength-hiddensong
  [hidden song strength]
  (let [res (q '[:find ?c :in $ ?s ?hid :where
                 [?c :nn/hiddennode ?hid]
                 [?c :nn/song ?s]] (db conn) song hidden)]
    (if (empty? res)
      @(d/transact conn [{:db/id (d/tempid :db.part/user)
                          :nn/song song
                          :nn/hiddennode hidden
                          :nn/constrength (float strength)}])
      @(d/transact conn [{:db/id (ffirst res)
                          :nn/constrength (float strength)}]))))

(defn generate-hidden-node
  [words songs]
  (let [hidden (apply str (apply conj words songs))
        res (q '[:find ?n :in $ ?hid :where
                 [?n :nn/hiddennode ?hid]] (db conn) hidden)]
    (if (empty? res)
      (do (dorun (map #(set-strength-wordhidden % hidden (float (/ 1 (count words)))) words))
        (dorun (map #(set-strength-hiddensong hidden % (float 0.1)) songs))))))

(defn get-all-hidden
  [words songs]
  (let [h1 (reduce (fn [res word] 
                     (apply merge res (first (q '[:find ?hn :in $ ?w :where
                                                  [?c :nn/hiddennode ?hn]
                                                  [?c :nn/word ?w]] (db conn) word)))) #{} words)
        h2 (reduce (fn [res song] 
                     (apply merge res (first (q '[:find ?hn :in $ ?s :where
                                                  [?c :nn/hiddennode ?hn]
                                                  [?c :nn/song ?s]] (db conn) song)))) #{} songs)]
    (vec (apply merge h1 h2))))

;for view only:

(defn get-songs-number
  []
  (count (q '[:find ?s :where [?s :song/song]] (db conn))))

(defn get-links-number
  []
  (count (q '[:find ?l :where [?l :link/visited "false"]] (db conn))))

(defn get-visited-links-number
  []
  (count (q '[:find ?l :where [?l :link/visited "true"]] (db conn))))


;for tests only:

(defn remove-link
  [link]
  (let [lid (ffirst (q '[:find ?id :in $ ?link :where
                 [?id :link/link ?link]] (db conn) link))]
    @(d/transact conn [[:db.fn/retractEntity lid]])))

(defn remove-song
  [song]
  (let [sid (ffirst (q '[:find ?id :in $ ?song :where
                         [?id :song/song ?song]] (db conn) song))]
    @(d/transact conn [[:db.fn/retractEntity sid]])))

(defn remove-user
  [username password]
  (let [uid (ffirst (q '[:find ?id :in $ ?user ?pass :where
                         [?id :user/username ?user]
                         [?id :user/password ?pass]] (db conn) username password))]
    @(d/transact conn [[:db.fn/retractEntity uid]])))




